/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Ucop/Net/TcpServer.h"
#include <cassert>
#include <memory>
#include "Ucop/System/Error.h"
#include "Ucop/Net/SocketApi.h"
using Ucop::System::Mutex;


namespace Ucop {
namespace Net {

    const static float MPA = 0.9f;

    TcpServer::TcpServer(TcpConnectionFactory *_ConnFactory)
        : _Mylistener(NULL)
        , _MyconnidGen(0)
        , _Myconnections()
        , _MyconnectionsLock()
        , _Myconnf(_ConnFactory)
        , _MymainWorker()
        , _MyaddtionalWorkerNum(0)
        , _MyaddtionalWorkers()
        , _MyerrorCode(0)
        , _MyerrorString("")
    {
        //UCOP_ASSERT(_ConnFactory);
    }

    TcpServer::~TcpServer(void)
    {
        _Uninitialize();
    }

    void TcpServer::SetConnectionFactory(TcpConnectionFactory *_ConnFactory)
    {
        UCOP_ASSERT(!_Myconnf);
        _Myconnf.reset(_ConnFactory);
    }

    bool TcpServer::Start(const char *_Host, int _Port, int _Backlog, int _AddWorkerNum /* = 0 */)
    {
        UCOP_ASSERT(_Myconnf);
        _MyaddtionalWorkerNum = _AddWorkerNum;

        do
        {
            /* Start main worker */
            if (!_MymainWorker.Start()) {
                break;
            }

            /* Start listener */
            _Mylistener.reset(new TcpListener(_MymainWorker.AcquireBase(), &TcpServer::_OnAccept, this));
            if (!(_Mylistener && _Mylistener->Open(_Host, _Port, _Backlog))) {
                break;
            }

            /* Create addtional workers */
            if (_AddWorkerNum > 0 && !_CreateAddtionalWorkers(_AddWorkerNum)) {
                break;
            }

            return true;
        } while (false);

        _GenErrorInfo();

        return false;
    }

    void TcpServer::Stop(void)
    {
        /* Stop listener */
        _Mylistener->Close();

        /* Stop addtional workers */
        _StopAddtionalWorkers();

        /* Stop main worker */
        _MymainWorker.Stop();

        Join();

        /* Close all connections */
        {
            SocketMap::iterator iter = _Myconnections.begin();
            for (; iter != _Myconnections.end(); ++iter)
            {
                boost::scoped_ptr<TcpSocket> _Sock(iter->second);
                _Sock->_Myhandler->OnClose();
            }
            _Myconnections.clear();
        }
    }

    void TcpServer::Join(void)
    {
        // join addtional workers
        if (_MyaddtionalWorkerNum > 0 && _MyaddtionalWorkers)
        {
            for (int x = 0; x < _MyaddtionalWorkerNum; ++x)
            {
                if (_MyaddtionalWorkers[x] != NULL) {
                    _MyaddtionalWorkers[x]->Join();
                }
            }
        }

        /* Join main worker */
        _MymainWorker.Join();
    }

    void TcpServer::_Uninitialize(void)
    {
        _CloseAddtionalWorkers();
    }

    void TcpServer::_GenErrorInfo(void)
    {
        _MyerrorCode = System::GetLastError();
        System::GetErrorString(_MyerrorString, _MyerrorCode);
    }

    void TcpServer::_CloseSocket(TcpSocket *_Sock)
    {
        UCOP_ASSERT(_Sock != NULL);
        {
            UCOP_LOCK_GURAD_ASSERT(_MyconnectionsLock);
            SocketMap::iterator iter = _Myconnections.find(_Sock->id());
            UCOP_ASSERT(iter != _Myconnections.end());
            if (iter != _Myconnections.end())
            {
                boost::scoped_ptr<TcpSocket> _Sock(iter->second);
                _Sock->_Myhandler->OnClose();
                _Sock->_Myrunnable->ReleaseBase();
                _Myconnections.erase(iter);
            }
        }
    }

    bool TcpServer::_CreateAddtionalWorkers(int _AddWorkerNum)
    {
        do
        {
            _MyaddtionalWorkers.reset(new TcpRunnable * [MAX_WORKERS]);
            if (!_MyaddtionalWorkers) {
                return false;
            }
            memset(_MyaddtionalWorkers.get(), 0, sizeof(TcpRunnable*)* MAX_WORKERS);

            int _NumWorkers = 0;
            for (; _NumWorkers < _AddWorkerNum; ++_NumWorkers)
            {
                std::auto_ptr<TcpRunnable> _Worker(new TcpRunnable());
                if (!_Worker.get()) {
                    return false;
                }

                if (_Worker->Start()) {
                    _MyaddtionalWorkers[_NumWorkers] = _Worker.release();
                    continue;
                }
            }
        } while (false);

        return true;
    }

    void TcpServer::_CloseAddtionalWorkers(void)
    {
        if (_MyaddtionalWorkerNum > 0 && _MyaddtionalWorkers)
        {
            for (int x = 0; x < _MyaddtionalWorkerNum; ++x)
            {
                boost::scoped_ptr<TcpRunnable> _Runnable(_MyaddtionalWorkers[x]);
                if (_Runnable) {
                    _Runnable->Stop();
                    _Runnable->Join();
                    _MyaddtionalWorkers[x] = NULL;
                }
            }
            _MyaddtionalWorkers.reset();
        }
    }

    void TcpServer::_StopAddtionalWorkers(void)
    {
        if (_MyaddtionalWorkerNum > 0 && _MyaddtionalWorkers)
        {
            for (int x = 0; x < _MyaddtionalWorkerNum; ++x)
            {
                if (_MyaddtionalWorkers[x] != NULL) {
                    _MyaddtionalWorkers[x]->Stop();
                }
            }
        }
    }

    void TcpServer::_OnAccept(evutil_socket_t _Sock, struct sockaddr *_SockAddr, int _AddrLen, void *_Userptr)
    {
        TcpServer *_Server = static_cast<TcpServer*>(_Userptr);
        UCOP_ASSERT(_Server);
        _Server->_DoAccept(_Sock, _SockAddr, _AddrLen, _Userptr);
    }

    void TcpServer::_DoAccept(evutil_socket_t _Sock, struct sockaddr * _SockAddr, int _AddrLen, void *)
    {
        do
        {
            UCOP_LOCK_GURAD_ASSERT(_MyconnectionsLock);

            /* Find a connection id */
            int _ConnId = ++_MyconnidGen;
            if (_Myconnections.find(_ConnId) != _Myconnections.end()) {
                break;
            }

            /* Accept the connection */
            std::auto_ptr<TcpSocket> _NewSock(new TcpSocket());
            std::auto_ptr<TcpConnection> _NewConn(_Myconnf->Create());
            if (_NewSock.get() && _NewConn.get())
            {
                TcpRunnable *_Worker = _FindWorker();
                if (_NewSock->Open(_Worker->AcquireBase(), _Sock, _SockAddr, _AddrLen))
                {
                    _NewConn->_SetPeer(_NewSock.get());
                    _NewSock->_SetId(_ConnId);
                    _NewSock->_SetOwner(this);
                    _NewSock->_SetThread(_Worker);
                    _NewSock->_SetEventHandler(_NewConn.release());
                    _NewSock->_Myhandler->OnOpen();
                    _Myconnections[_ConnId] = _NewSock.release();
                    return;
                }
            }

            /* Release the connection id */
            --_MyconnidGen;
        } while (false);

        CloseSocket(_Sock);
    }

    TcpRunnable *TcpServer::_FindWorker(void)
    {
        if (_MyaddtionalWorkerNum > 0)
        {
            int _WorkerIdx = 0;
            int _MinLoad = INT_MAX;

            /* Find most free attional worker */
            for (int x = 0; x < _MyaddtionalWorkerNum; ++x)
            {
                int _WorkerLoad = _MyaddtionalWorkers[x]->GetLoad();
                if (_WorkerLoad < _MinLoad)
                {
                    _MinLoad = _WorkerLoad;
                    _WorkerIdx = x;
                }
            }

            int _MainLoad = _MymainWorker.GetLoad();
            if (_MainLoad > (int)(_MinLoad * MPA)) {
                return _MyaddtionalWorkers[_WorkerIdx];
            }
        }

        return &_MymainWorker;
    }

}}